namespace AgentJohnson.CodeAnnotations
{
  using System;
  using System.Linq;
  using JetBrains.Annotations;
  using Resharper.Abstractions.CodeAnnotations;
  using Resharper.Abstractions.CodeDom.Statements;
  using Resharper.Abstractions.Daemons;

  /// <summary>Defines the <see cref="CodeAnnotationQuickFix"/> class.</summary>
  [QuickFix]
  public class CodeAnnotationQuickFix : QuickFixBase
  {
    #region Constructors and Destructors

    /// <summary>Initializes a new instance of the <see cref="CodeAnnotationQuickFix"/> class.</summary>
    /// <param name="highlight">The highlight.</param>
    public CodeAnnotationQuickFix(CodeAnnotationHighlight highlight) : base(highlight)
    {
      this.Model = highlight.Model;
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets or sets the code annotation model.
    /// </summary>
    /// <value>The code annotation model.</value>
    [NotNull]
    protected CodeAnnotationModel Model { get; set; }

    #endregion

    #region Methods

    /// <summary>Determines whether this instance can execute the specified action context.</summary>
    /// <param name="context">The action context.</param>
    /// <returns><c>true</c> if this instance can execute the specified action context; otherwise, <c>false</c>.</returns>
    protected override bool CanFix(CanFixContext context)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      return this.Model.CanFix;
    }

    /// <summary>Executes the action.</summary>
    /// <param name="context">The execute context.</param>
    protected override void Fix(FixContext context)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      this.FixAnnotations();
      this.FixReturn();
      this.RemoveAssertions();
      this.FixAssertions(context);

      if (this.Model.Body != null)
      {
        context.Formatter.Format(this.Model.Body);
      }
    }

    /// <summary>Gets the text.</summary>
    /// <returns>Returns the text.</returns>
    protected override string GetText()
    {
      return "Annotate parameters and check for nulls [Agent Johnson]";
    }

    /// <summary>Fixes the parameters.</summary>
    private void FixAnnotations()
    {
      foreach (var parameterDescriptor in this.Model.Parameters)
      {
        var parameter = parameterDescriptor.Parameter;
        if (parameter == null)
        {
          continue;
        }

        if (parameterDescriptor.ExpectedAttribute != parameterDescriptor.AppliedAttribute)
        {
          parameter.Annotate(parameterDescriptor.ExpectedAttribute);
        }
      }
    }

    /// <summary>Fixes the return.</summary>
    private void FixReturn()
    {
      if (this.Model.ExpectedReturn != this.Model.AppliedReturn)
      {
        this.Model.TypeMember.Annotate(this.Model.ExpectedReturn);
      }
    }

    /// <summary>Inserts the assertions.</summary>
    /// <param name="context">The context.</param>
    private void FixAssertions(FixContext context)
    {
      var body = this.Model.Body;
      if (body == null)
      {
        return;
      }

      Statement last = null;

      foreach (var parameterDescriptor in this.Model.Parameters.Reverse())
      {
        var code = parameterDescriptor.Code;
        if (string.IsNullOrEmpty(code))
        {
          continue;
        }

        var statement = context.Factory.CreateStatement(code);

        body.AddStatementBefore(statement);

        if (last == null)
        {
          last = statement;
          body.AddBlankLineAfter(statement);
        }
      }
    }

    /// <summary>Removes the assertions.</summary>
    private void RemoveAssertions()
    {
      var body = this.Model.Body;
      if (body == null)
      {
        return;
      }

      foreach (var parameterDescriptor in this.Model.Parameters)
      {
        if (parameterDescriptor.Assertion != null)
        {
          body.Remove(parameterDescriptor.Assertion);
        }
      }
    }

    #endregion
  }
}